package com.atguigu.gulimall.item.service.impl;


import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gulimall.cache.annotation.MallCache;
import com.atguigu.gulimall.cache.service.CacheOpsService;
import com.atguigu.gulimall.feign.product.SkuDetailFeignClient;
import com.atguigu.gulimall.feign.search.SearchFeignClient;
import com.atguigu.gulimall.item.service.SkuDetailService;
import com.atguigu.gulimall.product.entity.SkuImage;
import com.atguigu.gulimall.product.entity.SkuInfo;
import com.atguigu.gulimall.product.entity.SpuSaleAttr;
import com.atguigu.gulimall.web.vo.SkuDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author nicc
 * @version 1.0
 * @className SkuDetailServiceImpl
 * @date 2022-10-03 14:37
 */
@Service
@Slf4j
public class SkuDetailServiceImpl implements SkuDetailService {

    //byte
    //BloomFilter<Long> bloomFilter = null; //1mb

//    @Autowired
//    RBloomFilter<Object> skuIdBloom;

    @Autowired
    SkuDetailFeignClient skuDetailFeignClient;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    RedissonClient redisson;

    @Autowired
    CacheOpsService cacheOps;

    //原理？AQS-基于CAS
//    ReentrantLock lock = new ReentrantLock();

    @Autowired
    private ThreadPoolExecutor poolExecutor;


    @PostConstruct//这个组件创建好以后会执行这个方法
    //分布式布隆
    void init(){
//        log.info("项目启动：正在初始化分布式布隆过滤器");
//        skuIdBloom = redisson.getBloomFilter(RedisConst.BLOOM_SKUID);
//
//        //1.初始化布隆过滤器
//        if(!skuIdBloom.isExists()){
//            skuIdBloom.tryInit(1000000, 0.0000001);
//        }
//
//        //2.添加数据到布隆过滤器
//        //获取到所有skuId；
//        Result<List<Long>> skuId = skuDetailFeignClient.getAllSkuId();
//        skuId.getData().forEach(item -> {
//            skuIdBloom.add(item);
//        });
//
//        log.info("布隆过滤器初始化完成... 49:{} 77:{}",skuIdBloom.contains(49L),skuIdBloom.contains(77L));
    }


//本地布隆
//    void init(){
//        log.info("项目启动：正在初始化本地布隆过滤器");
//        bloomFilter = BloomFilter.create(Funnels.longFunnel(), 5000000, 0.0000001);
//        //获取到所有skuId；
//        Result<List<Long>> skuId = skuDetailFeignClient.getAllSkuId();
//        skuId.getData().forEach(item->{
//            bloomFilter.put(item);
//        });
//        log.info("布隆过滤器初始化完成... 49:{} 77:{}",bloomFilter.mightContain(49L),bloomFilter.mightContain(77L));
//    }


    public SkuDetailVo skuDetailWithRedisLock(Long skuId) {
        //加入缓存  5000000  600w+
        //1、Object data = cache.get(skuId)  //88 - x   600xxx- x
        //  if(data == null) //回源
        //       if(skuId.contains(skuId)) //数据库中有此记录 回源 else return null;
        // 有： return data;
        log.info("商品详情查询开始： {}", skuId);
        String uuid = UUID.randomUUID().toString();
        //1、加锁：都去redis占坑。  占坑 + 过期时间（自动解锁） = 原子命令
//        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock","1");

        // 49  50  51

        //锁要粒度设计细； 并发性能越高
        Boolean lock = redisTemplate.opsForValue()
                .setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);

        if (lock) {
            //抢锁成功。回源查询数据
//            redisTemplate.expire("lock",10,TimeUnit.SECONDS); 这样不原子
            System.out.println("执行业务");

            //业务期间断电。只要redis能自动解锁，就不怕业务断电。
            //业务超长、临界状态，可能都会导致删除别人的锁
            //自动续期+可重入锁？ 所有的锁都应该设计为 可重入的，否则可能会有死锁问题
            // a () {
            //   b() {
            //   }
            // }


            //2、解锁：防止解掉别人的锁   比较锁值 + 删除锁 = 原子操作
//            String lockValue = redisTemplate.opsForValue().get("lock");
//            if(uuid.equals(lockValue)){
//                redisTemplate.delete("lock");
//            }
            //redis单线程  "100行"  lua 脚本  if redis.call(get,"lock") == uuid then redis.call("lock")
            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                    "then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            //返回0，代表删锁失败：1）、锁已经被删了  2）、锁是别人的锁
            //返回1，删锁成功，删除了自己的锁
            Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                    Arrays.asList("lock"), uuid);
            if (result == 0) {
                log.error("这个锁是别人的，不能删");
            } else {
                log.info("删除了自己的锁");
            }

        } else {
            //抢锁失败。等待500ms，重新查询缓存
        }
        //1000w并发进来，查询了 1~1000w的所有商品


        return null;
    }


    //从远程查询商品
    //TODO 以后不应该直接启动异步任务，而是把异步任务交给线程池执行
    private SkuDetailVo getSkuDetailFromRpc(Long skuId) {
        SkuDetailVo data = new SkuDetailVo();

        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            //1、查询出sku_info信息
            Result<SkuInfo> skuInfo = skuDetailFeignClient.getSkuInfo(skuId);
            SkuInfo info = skuInfo.getData();
            //设置好skuinfo的数据返回
            data.setSkuInfo(info);
            return info;
        },poolExecutor);

        //2、查询当前sku的图片列表
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync(sku -> {
            Result<List<SkuImage>> skuImages = skuDetailFeignClient.getSkuImages(skuId);
            List<SkuImage> images = skuImages.getData();
            sku.setSkuImageList(images);
        },poolExecutor);

        //3、查询sku的分类信息
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync((sku) -> {
            Result<SkuDetailVo.CategoryView> categoryView = skuDetailFeignClient.getCategoryView(sku.getCategory3Id());
            data.setCategoryView(categoryView.getData());
        },poolExecutor);

        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            //4、查询sku的价格信息。为了得到最新价格，每次都再查一遍
            Result<BigDecimal> skuPrice = skuDetailFeignClient.getSkuPrice(skuId);
            data.setPrice(skuPrice.getData());
        },poolExecutor);

        //5、查询sku的销售属性
        CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync(res -> {
            Long spuId = res.getSpuId();
            Result<List<SpuSaleAttr>> value = skuDetailFeignClient.getSpuSaleAttrAndValue(spuId, skuId);
            data.setSpuSaleAttrList(value.getData());
        },poolExecutor);

        //6、json 就是 map（"119|120":49,"122:123":50）、javaBean
        CompletableFuture<Void> valueJsonFuture = skuInfoFuture.thenAcceptAsync(res -> {
            Result<String> skuJson = skuDetailFeignClient.getSpuValuesSkuJson(res.getSpuId());
            data.setValuesSkuJson(skuJson.getData());
        },poolExecutor);

        //以上任务全部运行结束
        CompletableFuture.allOf(imageFuture,categoryFuture,priceFuture,saleAttrFuture,valueJsonFuture).join();

        return data;
    }


    //闭锁 + 线程池 实现
//    private SkuDetailVo getSkuDetailFromRpc(Long skuId) {
//        SkuDetailVo data = new SkuDetailVo();
//        CountDownLatch count = new CountDownLatch(2);
//
//        //异步优化
//        //异步编排：CompletableFuture
//        //多异步任务的时候，异步之间有比较复杂的关系，可以通过异步编排，快速编写出他们之间的关系。
//
//        poolExecutor.execute(() -> {
//            //1、查询出sku_info信息
//            Result<SkuInfo> skuInfo = skuDetailFeignClient.getSkuInfo(skuId);
//
//            //2、查询当前sku的图片列表
//            Result<List<SkuImage>> skuImages = skuDetailFeignClient.getSkuImages(skuId);
//            SkuInfo info = skuInfo.getData();
//            info.setSkuImageList(skuImages.getData());
//
//            //设置好skuinfo的数据返回
//            data.setSkuInfo(info);
//
//            //3、查询sku的分类信息
//            Result<SkuDetailVo.CategoryView> categoryView = skuDetailFeignClient.getCategoryView(info.getCategory3Id());
//            data.setCategoryView(categoryView.getData());
//
//            //5、查询sku的销售属性
//            Long spuId = info.getSpuId();
//            Result<List<SpuSaleAttr>> value = skuDetailFeignClient.getSpuSaleAttrAndValue(spuId, skuId);
//            data.setSpuSaleAttrList(value.getData());
//
//            //6、json 就是 map（"119|120":49,"122:123":50）、javaBean
//            Result<String> skuJson = skuDetailFeignClient.getSpuValuesSkuJson(spuId);
//            data.setValuesSkuJson(skuJson.getData());
//
//            count.countDown();
//        });
//
//        poolExecutor.execute(() -> {
//            //4、查询sku的价格信息。为了得到最新价格，每次都再查一遍
//            Result<BigDecimal> skuPrice = skuDetailFeignClient.getSkuPrice(skuId);
//            data.setPrice(skuPrice.getData());
//
//            count.countDown();
//        });
//
//        try {
//            //确保异步任务均执行完毕
//            log.info("正在回源，请稍后...");
//            count.await();
//            log.info("回源完毕");
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
//
//        return data;
//    }


    public SkuDetailVo skuDetailWithRedissionAndBloomFilter(Long skuId) {

        String key = RedisConst.SKU_DETAIL_CACHE_PREFIX + skuId;
        //1、先查询缓存
        SkuDetailVo data = cacheOps.getCacheData(key, SkuDetailVo.class); //基于异常机制。x的数据会被直接返回出去就
        //2、判断是否存在
        if (data != null) {
            //3、缓存中有直接返回
            return data;
        }

        //4、缓存中真没有：回源（缓存穿透、缓存击穿）
        boolean contain = redisson.getBloomFilter(RedisConst.BLOOM_SKUID).contains(skuId);
        if (!contain) {
            return null;
        }

        //5、100w问布隆49有没有？说有，开始回源（注意解决 击穿风险）
        //6、准备一个分布式锁解决击穿。同一个商品只放一个查询进去。注意：设计为细粒度的锁
        RLock lock = redisson.getLock(RedisConst.LOCK_PREFIX + skuId);//lock-49
        //7、试一下获取锁
        boolean b = lock.tryLock();
        if (b) {
            try {
                //8、拿到锁？回源; 双检查机制
                SkuDetailVo cacheData = cacheOps.getCacheData(key, SkuDetailVo.class);
                if (cacheData == null) {
                    //10、回源
                    log.info("商品详情回源： {}", skuId);
                    SkuDetailVo fromRpc = getSkuDetailFromRpc(skuId);
                    cacheData = fromRpc;
                    //11、放入缓存
                    cacheOps.saveData(key, fromRpc, 7L, TimeUnit.DAYS);
                }
                return cacheData;
            } finally {
                //12、解锁
                lock.unlock();
            }
        } else {
            try {
                //9、没拿到锁，等500ms。直接获取缓存中的数据
                Thread.sleep(500);
                return cacheOps.getCacheData(key, SkuDetailVo.class);
            } catch (InterruptedException e) {

            }
        }

        return null;
    }

    @MallCache(
            cacheKey= RedisConst.SKU_DETAIL_CACHE_PREFIX + "#{#args[0]}",
            bloomKey = RedisConst.BLOOM_SKUID,
            bloomValue = "#{#args[0]}",
            ttl = 7L,
            unit = TimeUnit.DAYS
    ) //key可以兼容表达式完全动态
    @Override
    public SkuDetailVo skuDetail(Long skuId) {

        SkuDetailVo fromRpc = getSkuDetailFromRpc(skuId);

        return fromRpc;
    }

    @Autowired
    SearchFeignClient searchFeignClient;

    @Override
    public void incrHotScore(Long skuId) {
        //累积更新。skuId 每满 100 给 es 更一次  hotscore:49
        //返回最新叠加到的值
        Long increment = redisTemplate.opsForValue().increment(RedisConst.HOTSCORE + skuId);
        if(increment % 100 == 0){
            //远程让es更新 skuId=increment
            searchFeignClient.updateHotScore(skuId,increment);
        }
    }
}
